<?php
// +----------------------------------------------------------------------
// | INPHP
// +----------------------------------------------------------------------
// | Copyright (c) 2021 https://inphp.cc All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( https://opensource.org/licenses/MIT )
// +----------------------------------------------------------------------
// | Author: lulanyin <me@lanyin.lu>
// +----------------------------------------------------------------------
// |                        快捷DB操作
// +----------------------------------------------------------------------
namespace Inphp\DB;

use PDO;
use PDOException;
use PDOStatement;
use Swoole\Coroutine;

class DB
{
    /**
     * 数据库配置
     * @var array
     */
    public static array $config = [];

    /**
     * 获取数据库配置
     * @return array
     */
    public static function getConfig() : array
    {
        if(!empty(self::$config)){
            return self::$config;
        }
        if(defined("INPHP_DB_CONFIG")){
            self::$config = is_string(INPHP_DB_CONFIG) ? (is_file(INPHP_DB_CONFIG) ? require(INPHP_DB_CONFIG) : []) : (is_array(INPHP_DB_CONFIG) ? INPHP_DB_CONFIG : []);
        }
        return self::$config;
    }

    /**
     * @param $table
     * @param string $as
     * @return Query
     */
    public static function from($table, string $as = '') : Query
    {
        $query = new Query();
        return $query->from($table, $as);
    }

    /**
     * 记录日志，一般是记录错误信息
     * @param string $text 日志内容
     */
    public static function log(string $text)
    {
        self::getConnection()->log($text);
    }

    /**
     * 数据库连接对象（静态对象，保证全局只使用一次连接）
     * @var Connection|null
     */
    private static Connection|null $connection = null;

    /**
     * 获取数据库连接对象
     * @return Connection
     */
    public static function getConnection() : Connection
    {
        if(self::isSwoole()){
            $context = Coroutine::getContext();
            return $context['db_connection'] ?? ConnectionPools::getConnection();
        }else{
            if(is_null(self::$connection)){
                self::$connection = new Connection(self::getConfig());
            }
            return self::$connection;
        }
    }

    /**
     * 重置连接
     */
    public static function resetConnection()
    {
        if(self::isSwoole()){
            $context = Coroutine::getContext();
            if($context['db_connection']){
                $context['db_connection'] = null;
            }
        }else{
            self::$connection = null;
        }
    }

    /**
     * 事务开始
     */
    public static function begin()
    {
        $connection = self::getConnection();
        if(self::isSwoole()){
            //将该连接保存在协程上下文，以备后继所有的当前协程所用的数据操作，都使用该连接进行操作
            Coroutine::getContext()['db_connection'] = $connection;
        }
        $connection->begin();
    }

    /**
     * 事务回滚
     */
    public static function rollback()
    {
        $connection = self::getConnection();
        $connection->rollback();
        //清除上下文
        if(self::isSwoole()){
            Pool::putPool($connection);
            Coroutine::getContext()['db_connection'] = null;
        }
    }

    /**
     * 提交事务
     */
    public static function commit(){
        $connection = self::getConnection();
        $connection->commit();
        //清除上下文
        if(self::isSwoole()){
            Pool::putPool($connection);
            Coroutine::getContext()['db_connection'] = null;
        }
    }

    /**
     *
     * @param string $type
     * @return PDO|false
     */
    public static function getPdo(string $type = 'read') : PDO|false
    {
        return self::getConnection()->getPdo($type);
    }

    /**
     * 执行SQL语句
     * @param string $sql
     * @param array|null $params
     * @return PDOStatement|false
     */
    public static function execute(string $sql, array|null $params = null) : PDOStatement|false
    {
        $con = self::getConnection();
        $sql = trim($sql);
        $type = stripos($sql, "SELECT") === 0 ? "read" : "write";
        $pdo = $con->getPdo($type);
        $execute_sql = [
            "time"  => date("Y/m/d H:i:s"),
            "sql"   => $sql,
            "params"=> $params,
            "success" => false,
            "affect_rows" => 0
        ];
        try{
            $stmt = $pdo->prepare($sql);
            if($stmt->execute($params)){
                if($type == 'read'){
                    $stmt->setFetchMode($con->fetch_model);
                }
                $execute_sql['success'] = true;
                return $stmt;
            }else{
                $code = intval($stmt->errorCode());
                throw new PDOException($stmt->errorInfo()[2]."<br>query : ".$stmt->queryString."<br>code source : ".$code, $code);
            }
        }catch (PDOException $exception){
            $con->setError($exception->getCode(), $exception->errorInfo);
        }
        $con->log(json_encode($execute_sql, JSON_UNESCAPED_UNICODE), true);
        self::releaseConnection($con);
        return isset($stmt) ? $stmt : false;
    }

    /**
     * 释放连接
     * @param Connection $connection
     */
    public static function releaseConnection(Connection $connection)
    {
        if(!$connection->in_transaction && self::isSwoole())
        {
            ConnectionPools::putConnection($connection);
        }
    }

    /**
     * 获取表前缀
     * @param string $type
     * @return string
     */
    public static function getTablePrefix(string $type = 'read') : string
    {
        $configs = self::getConfig();
        $config = $configs[$type] ?? [];
        return $config['table_prefix'] ?? ($configs['table_prefix'] ?? '');
    }
    
    /**
     * 当前是否属性swoole环境
     * @return bool
     */
    public static function isSwoole(): bool
    {
        !defined("INPHP_SERVICE_PROVIDER") && define("INPHP_SERVICE_PROVIDER", 'fpm');
        return INPHP_SERVICE_PROVIDER === 'swoole';
    }

    /**
     * 环境
     * @var string
     */
    private static string $env_version = 'release';

    /**
     * 修改运行环境
     * @param string $version
     */
    public static function setEnvVersion(string $version)
    {
        if(self::isSwoole())
        {
            Coroutine::getContext()['db_version'] = $version;
        }
        else
        {
            self::$env_version = $version;
        }
    }

    /**
     * 设置为开发模式
     */
    public static function setEnvDevelop()
    {
        self::setEnvVersion('develop');
    }

    /**
     * 设置为生产模式
     */
    public static function setEnvRelease()
    {
        self::setEnvVersion('release');
    }

    /**
     * 是否是开发模式
     * @return bool
     */
    public static function isEnvDevelop(): bool
    {
        return self::getEnvVersion() == 'develop';
    }

    /**
     * 是否是生产模式
     * @return bool
     */
    public static function isEnvRelease(): bool
    {
        return self::getEnvVersion() == 'release';
    }

    /**
     * 获取当前的环境
     * @return string
     */
    public static function getEnvVersion(): string
    {
        if(self::isSwoole())
        {
            $context = Coroutine::getContext();
            return $context['db_version'] ?? 'release';
        }
        else
        {
            return self::$env_version;
        }
    }
}